﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Types;
using System.Data.SqlTypes;

namespace MyFloridaHouse.libServiceBusinessRules
{
    /// <summary>
    /// This class is used for generalizing the number of points on a geometry shape
    /// It only handles polygons and multipolygons making the assumption that all of the building 
    /// shapes are one of these two types
    /// </summary>
    public static class GeometryPointReduction
    {

        public static SqlGeometry CreateNewGeomFromReducedShape(List<polygon> polygonsCleaned)
        {
            SqlGeometry newGeom = new SqlGeometry();
            StringBuilder sb = new StringBuilder();
            if (polygonsCleaned.Count() > 1)
            {
                sb.Append("MULTIPOLYGON((");
            }
            else if (polygonsCleaned.Count() == 1)// single poly
            {
                sb.Append("POLYGON(");
            }
            else //is empty return empty geometry
            {
                return newGeom;
            }
            int polyInx = 0;
            foreach (polygon poly in polygonsCleaned)
            {
                if (polyInx == 0)
                {
                    sb.Append("(");
                }
                else
                {
                    sb.Append(",(");
                }
                int ptInx = 01;
                foreach (SqlGeometry pt in poly.polyPoints) //exterior
                {
                    if (ptInx < poly.polyPoints.Count)
                    {
                        sb.Append(pt.STX.ToString() + " " + pt.STY.ToString() + ",");
                    }
                    else //last one no comma
                    {
                        sb.Append(pt.STX.ToString() + " " + pt.STY.ToString());
                    }
                    ptInx++;
                }
                sb.Append(")");
                foreach (interiorRing ir in poly.irRingList)
                {
                    sb.Append(",(");
                    ptInx = 01;
                    foreach (SqlGeometry pt in ir.irPoints)
                    {
                        if (ptInx < ir.irPoints.Count)
                        {
                            sb.Append(pt.STX.ToString() + " " + pt.STY.ToString() + ",");
                        }
                        else //last one no comma
                        {
                            sb.Append(pt.STX.ToString() + " " + pt.STY.ToString());
                        }
                        ptInx++;
                    }
                    sb.Append(")");
                }
                polyInx++;
            }
            if (polygonsCleaned.Count() > 1)
            {
                sb.Append("))");
            }
            else // single poly
            {
                sb.Append(")");
            }
            SqlChars sc = new SqlChars(sb.ToString());
            newGeom = SqlGeometry.STGeomFromText(sc, 4326);
            //newGeom = SqlGeometry.STGeomFromText(sc, 4326);
            return newGeom;
        }


        public static List<polygon> CleanPolygons(List<polygon> polygons)
        {
            List<polygon> listToReturn = new List<polygon>();
            foreach (polygon poly in polygons)
            {
                SqlGeometry startPt = poly.polyPoints[0];
                SqlGeometry prevPt = startPt;
                int ptInx = 0;
                List<SqlGeometry> workingPolyPointList = new List<SqlGeometry>();
                foreach (SqlGeometry pt in poly.polyPoints) //exterior
                {
                    if (!pt.STEquals(prevPt))
                    {
                        //poly.polyPoints.RemoveAt(ptInx);
                        workingPolyPointList.Add(pt);
                    }
                    prevPt = pt;
                    ptInx++;
                }
                var uniquePolyPointList = workingPolyPointList.Distinct(new DistinctPointComparer());
                poly.polyPoints = uniquePolyPointList.ToList();
                if (poly.polyPoints.Count() > 3) //this one is reduced out at this zoom level
                {
                    if (!poly.polyPoints[0].STEquals(startPt))//verify we keep start point the same w/ out wiping out current point in 0 spot
                    {
                        poly.polyPoints.Insert(0, startPt);
                    }
                    if (!poly.polyPoints[poly.polyPoints.Count - 1].STEquals(startPt))//verify we keep end point the same
                    {
                        poly.polyPoints.Add(startPt);
                    }
                    listToReturn.Add(poly);
                }
                if (poly.polyPoints.Count() > 3) //this one is reduced out at this zoom level
                {
                    List<interiorRing> irListToReturn = new List<interiorRing>();
                    foreach (interiorRing ir in poly.irRingList) //interiors
                    {
                        startPt = ir.irPoints[0];
                        prevPt = startPt;
                        int irIndx = 0;
                        List<SqlGeometry> workingIRPointList = new List<SqlGeometry>();
                        foreach (SqlGeometry pt in ir.irPoints) //exterior
                        {
                            if (!pt.STEquals(prevPt))
                            {
                                //ir.irPoints.RemoveAt(irIndx);
                                workingIRPointList.Add(pt);
                            }
                            prevPt = pt;
                            irIndx++;
                        }
                        //ir.irPoints = workingIRPointList;
                        var uniqueirPointList = workingIRPointList.Distinct(new DistinctPointComparer());
                        ir.irPoints = uniqueirPointList.ToList();
                        if (ir.irPoints.Count() > 3)
                        {
                            if (!ir.irPoints[0].STEquals(startPt))//verify we keep start point
                            {
                                //ir.irPoints[0] = startPt; //jdm this was a mistake fixed on 3/24 
                                ir.irPoints.Insert(0, startPt);
                            }
                            if (!ir.irPoints[ir.irPoints.Count - 1].STEquals(startPt))//verify we keep end point the same
                            {
                                ir.irPoints.Add(startPt);
                            }
                            irListToReturn.Add(ir);
                        }
                    } //foreach (interiorRing ir in poly.irRingList)
                    poly.irRingList.Clear();
                    poly.irRingList = irListToReturn;
                }
            } //foreach (polygon poly in polygons)
            return listToReturn;
        }


        public static List<polygon> GetPolygons(SqlGeometry geom, int level)
        {
            SqlInt32 numOfGeoms = geom.STNumGeometries();
            List<polygon> listOfPolys = new List<polygon>();
            for (int ng = 1; ng <= numOfGeoms; ng++)//looping through each geom
            {
                polygon newPolyForList = new polygon();
                SqlGeometry individualGeom = geom.STGeometryN(ng);//individual polygon
                newPolyForList.polyPoints = GetPolyPoints(individualGeom, level, true);
                newPolyForList.irRingList = GetInteriorRings(individualGeom, level);
                listOfPolys.Add(newPolyForList);
            }
            return listOfPolys;
        }


        public static List<SqlGeometry> GetPolyPoints(SqlGeometry individualGeom, int level, bool isExterior)
        {
            SqlGeometry ring;
            if (isExterior)
            {
                ring = individualGeom.STExteriorRing();
            }
            else
            {
                ring = individualGeom;
            }

            List<SqlGeometry> processedPoints = new List<SqlGeometry>();
            for (int er = 1; er <= ring.STNumPoints(); er++)
            {
                SqlGeometry point = ring.STPointN(er);
                double stx = double.Parse(point.STX.ToString());
                double xp = .01098632 / Math.Pow(2, level - 7);
                double x = Convert.ToInt32(Math.Abs(stx) / xp) * xp;
                double sty = double.Parse(point.STY.ToString());
                double yp = .009698274 / Math.Pow(2, level - 7);
                double y = Convert.ToInt32(Math.Abs(sty) / yp) * yp;
                SqlChars ppWkt = new SqlChars("POINT(-" + x + " " + y + ")");
                SqlGeometry processedPoint = SqlGeometry.STPointFromText(ppWkt, 4326);
                //SqlGeometry processedPoint = SqlGeometry.STPointFromText(ppWkt, 4326);
                //processedPoints[er - 1] = processedPoint;
                processedPoints.Add(processedPoint);
            }
            return processedPoints;
        }

        public static List<interiorRing> GetInteriorRings(SqlGeometry individualGeom, int level)
        {
            SqlInt32 numOfInteriorRings = individualGeom.STNumInteriorRing();
            List<interiorRing> irList = new List<interiorRing>();
            if (numOfInteriorRings >= 1)//has interiors
            {
                for (int ir = 1; ir <= numOfInteriorRings; ir++)//looping through each interior ring if there are any
                {
                    interiorRing irNew = new interiorRing();
                    irNew.irPoints = GetPolyPoints(individualGeom.STInteriorRingN(ir), level, false);
                    irList.Add(irNew);
                }
            }
            return irList;
        }


        public class DistinctPointComparer : IEqualityComparer<SqlGeometry>
        {

            public bool Equals(SqlGeometry firstPt, SqlGeometry secondPt)
            {
                return firstPt.STX.ToString() == secondPt.STX.ToString() &&
                    firstPt.STY.ToString() == secondPt.STY.ToString();
            }

            public int GetHashCode(SqlGeometry obj)
            {
                return obj.STX.ToString().GetHashCode() ^
                    obj.STY.ToString().GetHashCode();
            }
        }




    }
}
